package org.ns.framework.datasource.mybatis;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.ns.basics.commons.aop.AopProxy;
import org.ns.basics.commons.aop.ProxyPointcut;
import org.ns.framework.datasource.anns.DBSource;
import org.ns.framework.datasource.config.DataSourceLoader;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
/**
 * MyBatis 数据源注入
 * 
 * @author zhangxingyu
 *
 */
public class MybatisDataSourceInjector implements BeanPostProcessor , ApplicationContextAware {
	
//    Map<String, SqlSessionFactory> sqlSessionFactoryMap = new ConcurrentHashMap<>();
    
    private ApplicationContext applicationContext;
    @Autowired
    DataSourceLoader dataSourceLoader;
//    @Autowired
//    MybatisConfigurationManage mybatisConfigurationManage;
//    @Autowired(required = false)
//    MybatisSqlSessionFactoryBean mybatisSqlSessionFactoryBean;
    @Autowired(required = false)
    Boolean ignoreInjectorPrefixVerify = false;
    @Autowired(required = false)
    MybatisMapperManage mybatisMapperManage;
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        Class<?> targetClass = AopUtils.getTargetClass(bean);
        String name = targetClass.getName();
        if (ignoreInjectorPrefixVerify == null || !ignoreInjectorPrefixVerify) {
	        // 不执行框架内的, 就是不给你用!.
	        if (name.startsWith("org.apache.") || name.startsWith("org.springframework.")) {
	        	return bean;
	        }
        }
        
        List<Field> fields = findFields(targetClass);
        if (fields.size() == 0) { // 必须存在DBSource标记的字段才可以继续下去.
        	return bean;
        }
        for (Field field : fields) {
            injector(field, bean);
        }
        return proxyTransation(beanName, bean, targetClass);
    }

    private void injector(Field field, Object bean) throws BeansException {
        Class<?> fieldType = field.getType();

        DBSource dbSource = AnnotatedElementUtils.findMergedAnnotation(field, DBSource.class);
        String datasourceName = dbSource.value();

        Object value = mybatisMapperManage.getMapper(datasourceName, fieldType);
        
        if (value != null) {
            if (!field.isAccessible()) {
                field.setAccessible(true);
            }
            try {
                field.set(bean, value);
            } catch (Exception e) {
                throw new BeanInitializationException("DBSource injector create fail", e);
            }
        }
    }


    

    private List<Field> findFields (Class<?> targetClass) {

        List<Field> fields = new ArrayList<>();
        Class<?> nextClass = targetClass;
        while (nextClass != null && nextClass != Object.class) {
            Field[] declaredFields = nextClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Class<?> fieldType = declaredField.getType();
                if (fieldType.getName().startsWith("com.mongodb")) { // 不可以处理 mongo 的数据源.
                	continue;
                }
                if (AnnotatedElementUtils.hasAnnotation(declaredField, DBSource.class)
                    && (fieldType.isInterface()
                        || fieldType == SqlSessionFactory.class
                        || fieldType == DataSource.class)
                ) {
                    fields.add(declaredField);
                }
            }

            nextClass = nextClass.getSuperclass();
        }
        return fields;
    }

    
	private List<Method> findMethods(Class<?> targetClass) {
		List<Method> methods = new ArrayList<>();
		Class<?> nextClass = targetClass;
		while (nextClass != null && nextClass != Object.class) {
			Method[] declaredMethods = nextClass.getDeclaredMethods();
			for (Method declaredMethod : declaredMethods) {
				int mod = declaredMethod.getModifiers();
				if (AnnotatedElementUtils.hasMetaAnnotationTypes(declaredMethod,
						"org.springframework.transaction.annotation.Transactional") && !Modifier.isAbstract(mod)) {
					methods.add(declaredMethod);
				}
			}
			nextClass = nextClass.getSuperclass();
		}
		return methods;
	}
	
	/**
	 * 代理注解事务.
	 * @param beanName
	 * @param bean
	 * @param targetClass
	 * @return
	 */
	private Object proxyTransation(String beanName, Object bean, Class<?> targetClass) {
		if (findMethods(targetClass).size() == 0) {
			return bean;
		}
		return AopProxy.proxy(bean, new ProxyPointcut(null, (method, clazz) -> {
			int mod = method.getModifiers();
			return AnnotatedElementUtils.hasMetaAnnotationTypes(method,
					"org.springframework.transaction.annotation.Transactional") && !Modifier.isAbstract(mod);
		}), new MybatisTransactionProxy());
	}

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
